using UnityEngine;

public class TerrainChunk
{
    /// <summary>
    /// 离玩家多近生成碰撞体
    /// </summary>
    private const float colliderGenerationDistanceThreshold = 10;   
    
    public event System.Action<TerrainChunk, bool> onVisibilityChanged;
    
    public Vector2 coord;

    private GameObject meshObject;
    private Vector2 sampleCentre;
    private Bounds bounds;

    private MeshRenderer meshRenderer;
    private MeshFilter meshFilter;
    MeshCollider meshCollider;

    private LodInfo[] detailLevels;
    private LODMesh[] lodMeshes;
    private int _colliderColliderLodIndex;

    private HeightMap _heightMap;
    private bool heightMapReceived;
    private int previousLODIndex = -1;
    private bool hasSetCollider;
    private float maxViewDst;

    private HeightMapSettings heightMapSettings;
    private MeshSettings meshSettings;
    private Transform viewer;

    public TerrainChunk(Vector2 coord, HeightMapSettings heightMapSettings, MeshSettings meshSettings, LodInfo[] detailLevels, int colliderLodIndex, Transform parent,Transform viewer, Material material)
    {
        this.coord = coord;
        this.detailLevels = detailLevels;
        this._colliderColliderLodIndex = colliderLodIndex;
        this.heightMapSettings = heightMapSettings;
        this.meshSettings = meshSettings;
        this.viewer = viewer;

        sampleCentre = coord * meshSettings.meshWorldSize / meshSettings.meshScale;
        Vector2 position = coord * meshSettings.meshWorldSize;
        bounds = new Bounds(position, Vector2.one * meshSettings.meshWorldSize);


        meshObject = new GameObject("Terrain Chunk");
        meshRenderer = meshObject.AddComponent<MeshRenderer>();
        meshFilter = meshObject.AddComponent<MeshFilter>();
        meshCollider = meshObject.AddComponent<MeshCollider>();
        meshRenderer.material = material;
        meshObject.transform.position = new Vector3(position.x, 0, position.y);
        meshObject.transform.SetParent(parent);
        // meshObject.transform.localScale = Vector3.one * mapGenerator.meshSettings.meshScale;
        SetVisible(false);

        lodMeshes = new LODMesh[detailLevels.Length];
        for (int i = 0; i < detailLevels.Length; i++)
        {
            lodMeshes[i] = new LODMesh(detailLevels[i].lod);
            lodMeshes[i].updateCallback += UpdateTerrainChunk;
            if (i == _colliderColliderLodIndex)
            {
                lodMeshes[i].updateCallback += UpdateCollisionMesh;
            }
        }

        maxViewDst = detailLevels[detailLevels.Length - 1].visibleDstThreshold;
        

    }

    public void Load()
    {
        ThreadedDataRequester.RequestData(
            () => HeightMapGenerator.GenerateHeightMap(meshSettings.numVertsPerLine, meshSettings.numVertsPerLine,
                heightMapSettings, sampleCentre), onHeightMapDataReceived);
    }
    
    void onHeightMapDataReceived(object MapData)
    {
        _heightMap = (HeightMap)MapData;
        heightMapReceived = true;

        UpdateTerrainChunk();
    }

    
    Vector2 viewerPosition
    {
        get
        {
            return new Vector2(viewer.position.x, viewer.position.z);
        }
    }

    public void UpdateTerrainChunk()
    {
        if (heightMapReceived)
        {
            float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));

            bool wasVisible = IsVisible();
            bool visible = viewerDstFromNearestEdge <= maxViewDst;

            if (visible)
            {
                int lodIndex = 0;

                for (int i = 0; i < lodMeshes.Length - 1; i++)
                {
                    if (viewerDstFromNearestEdge > detailLevels[i].visibleDstThreshold)
                    {
                        lodIndex = i + 1;
                    }
                    else
                    {
                        break;
                    }
                }

                if (lodIndex != previousLODIndex)
                {
                    LODMesh lodMesh = lodMeshes[lodIndex];
                    if (lodMesh.hasMesh)
                    {
                        previousLODIndex = lodIndex;
                        meshFilter.mesh = lodMesh.mesh;
                    }
                    else if (!lodMesh.hasRequestedMesh)
                    {
                        lodMesh.RequestMesh(_heightMap,meshSettings);
                    }
                }
            }

            if (wasVisible != visible)
            {
                SetVisible(visible);
                if (onVisibilityChanged != null)
                {
                    onVisibilityChanged(this, visible);
                }

            }
        }
    }

    /// <summary>
    /// 优化：尽可能晚创建碰撞体，调用频率高于网格创建
    /// </summary>
    public void UpdateCollisionMesh()
    {
        if (!hasSetCollider)
        {
            float sqrDstFromViewerToEdge = bounds.SqrDistance(viewerPosition);

            if (sqrDstFromViewerToEdge < detailLevels[_colliderColliderLodIndex].sqrVisibleDstThreshold)
            {
                if (!lodMeshes[_colliderColliderLodIndex].hasRequestedMesh)
                {
                    lodMeshes[_colliderColliderLodIndex].RequestMesh(_heightMap,meshSettings);
                }
            }

            if (sqrDstFromViewerToEdge < colliderGenerationDistanceThreshold * colliderGenerationDistanceThreshold)
            {
                if (lodMeshes[_colliderColliderLodIndex].hasMesh)
                {
                    meshCollider.sharedMesh = lodMeshes[_colliderColliderLodIndex].mesh;
                    hasSetCollider = true;
                }
            }
        }
    }

    public void SetVisible(bool visible)
    {
        meshObject.SetActive(visible);
    }

    public bool IsVisible()
    {
        return meshObject.activeSelf;
    }
}


class LODMesh
{
    public Mesh mesh;

    /// <summary>
    /// 是否已经请求生成网格
    /// </summary>
    public bool hasRequestedMesh;

    /// <summary>
    /// 是否已经接收到网格
    /// </summary>
    public bool hasMesh;

    int lod;

    public event System.Action updateCallback;

    public LODMesh(int lod)
    {
        this.lod = lod;
    }

    void OnMeshDataReceived(object meshDataObject)
    {
        mesh = ((MeshData)(meshDataObject)).CreateMesh();
        hasMesh = true;
        updateCallback?.Invoke();
    }

    public void RequestMesh(HeightMap heightMap, MeshSettings meshSettings)
    {
        hasRequestedMesh = true;
        ThreadedDataRequester.RequestData(() => MeshGenerator.GenerateTerrainMesh(heightMap.value, meshSettings, lod),
            OnMeshDataReceived);
    }
}

[System.Serializable]
public struct LodInfo
{
    //层次限制到5，给顶点数目提供更多选择
    [Range(0,MeshSettings.numSupportedLODs)]
    public int lod;
    public float visibleDstThreshold;
    //细节网格用于碰撞
    public bool useForCollider;

    public float sqrVisibleDstThreshold
    {
        get
        {
            return visibleDstThreshold * visibleDstThreshold;
        }
    }
}